package cn.edu.scut.hsrc.util;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Configuration {

	private static final Log LOG = LogFactory
			.getLog(Configuration.class);

	private static Configuration instance = null;

	private static Properties properties;

	private ClassLoader classLoader;
	{
		classLoader = Thread.currentThread().getContextClassLoader();
		if (classLoader == null) {
			classLoader = ClassLoader.getSystemClassLoader();
		}
	}

	// singleton
	private Configuration() {
		properties = new Properties();
		URL url = classLoader.getResource("hsrc.xml");
		loadConfiguration(url);
	}

	public static Configuration getInstance() {
		if (instance == null) {
			instance = new Configuration();
		}
		return instance;
	}

	private void loadConfiguration(URL resource) {

		// open up the XML file
		DocumentBuilderFactory factory = null;
		DocumentBuilder parser = null;
		Document document = null;
		try {
			factory = DocumentBuilderFactory.newInstance();
			parser = factory.newDocumentBuilder();
			document = parser.parse(resource.openStream());
		} catch (Exception e) {
			LOG.fatal("Configuration file: " + resource.toString() + " not found");
			return;
		}

		Element root = document.getDocumentElement();
		// get all the property nodes
		NodeList propertyList = root.getElementsByTagName("property");
		// iterate through the property
		for (int i = 0; i < propertyList.getLength(); i++) {
			Element property = (Element) propertyList.item(i);
			String name = null;
			String value = "";
			// for each property, get the name
			NodeList names = property.getElementsByTagName("name");
			if (names.getLength() == 1) {
				Element e = (Element) names.item(0);
				Node t = e.getFirstChild();
				name = t.getNodeValue();
			}

			// for each property, get the value
			NodeList values = property.getElementsByTagName("value");
			if (values.getLength() == 1) {
				Element e = (Element) values.item(0);
				Node t = e.getFirstChild();
				if (t != null)
					value = t.getNodeValue();
			}
			properties.setProperty(name, value);
			// System.out.println(name + ": " + value);
		}
	}

	/**
	 * Get the value of the <code>name</code> property, <code>null</code> if no
	 * such property exists.
	 * 
	 * @param name
	 *            the property name.
	 * @return the value of the <code>name</code> property, or null if no such
	 *         property exists.
	 */
	public String get(String name) {
		return properties.getProperty(name);
	}

	/**
	 * Get the value of the <code>name</code> property. If no such property
	 * exists, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value, or <code>defaultValue</code> if the property
	 *         doesn't exist.
	 */
	public String get(String name, String defaultValue) {
		String result = properties.getProperty(name, defaultValue);
		if (result == null || result.equalsIgnoreCase(""))
			result = defaultValue;
		return result; 
	}

	/**
	 * Get the value of the <code>name</code> property as an <code>int</code>.
	 * 
	 * If no such property exists, or if the specified value is not a valid
	 * <code>int</code>, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value as an <code>int</code>, or
	 *         <code>defaultValue</code>.
	 */
	public int getInt(String name, int defaultValue) {
		String valueString = properties.getProperty(name);
		if (valueString == null)
			return defaultValue;
		else
			return Integer.parseInt(valueString);
	}

	/**
	 * Get the value of the <code>name</code> property as a <code>long</code>.
	 * If no such property is specified, or if the specified value is not a
	 * valid <code>long</code>, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value as a <code>long</code>, or
	 *         <code>defaultValue</code>.
	 */
	public long getLong(String name, long defaultValue) {
		String valueString = properties.getProperty(name);
		if (valueString == null)
			return defaultValue;
		else
			return Long.parseLong(valueString);
	}

	/**
	 * Get the value of the <code>name</code> property as a <code>float</code>.
	 * If no such property is specified, or if the specified value is not a
	 * valid <code>float</code>, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value as a <code>float</code>, or
	 *         <code>defaultValue</code>.
	 */
	public float getFloat(String name, float defaultValue) {
		String valueString = properties.getProperty(name);
		if (valueString == null)
			return defaultValue;
		else
			return Float.parseFloat(valueString);
	}
	
	/**
	 * Get the value of the <code>name</code> property as a <code>double</code>.
	 * If no such property is specified, or if the specified value is not a
	 * valid <code>double</code>, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value as a <code>double</code>, or
	 *         <code>defaultValue</code>.
	 */
	public double getDouble(String name, double defaultValue) {
		String valueString = properties.getProperty(name);
		if (valueString == null)
			return defaultValue;
		else
			return Double.parseDouble(valueString);
	}

	/**
	 * Get the value of the <code>name</code> property as a <code>boolean</code>
	 * . If no such property is specified, or if the specified value is not a
	 * valid <code>boolean</code>, then <code>defaultValue</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @param defaultValue
	 *            default value.
	 * @return property value as a <code>boolean</code>, or
	 *         <code>defaultValue</code>.
	 */
	public boolean getBoolean(String name, boolean defaultValue) {
		String valueString = properties.getProperty(name);
		if ("true".equals(valueString))
			return true;
		else if ("false".equals(valueString))
			return false;
		else
			return defaultValue;
	}

	/**
	 * Get an input stream attached to the configuration resource with the given
	 * <code>name</code>.
	 * 
	 * @param name
	 *            configuration resource name.
	 * @return an input stream attached to the resource.
	 */
	public InputStream getConfResourceAsInputStream(String name) {
		try {
			URL url = getResource(name);

			if (url == null) {
				LOG.info(name + " not found");
				return null;
			} else {
				LOG.info("found resource " + name + " at " + url);
			}

			return url.openStream();
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Get a {@link Reader} attached to the configuration resource with the
	 * given <code>name</code>.
	 * 
	 * @param name
	 *            configuration resource name.
	 * @return a reader attached to the resource.
	 */
	public Reader getConfResourceAsReader(String name) {
		try {
			URL url = getResource(name);

			if (url == null) {
				LOG.info(name + " not found");
				return null;
			} else {
				LOG.info("found resource " + name + " at " + url);
			}

			return new InputStreamReader(url.openStream());
		} catch (Exception e) {
			return null;
		}
	}

	public URL getResource(String name) {
		return classLoader.getResource(name);
	}

	/**
	 * Get the comma delimited values of the <code>name</code> property as an
	 * array of <code>String</code>s. If no such property is specified then
	 * <code>null</code> is returned.
	 * 
	 * @param name
	 *            property name.
	 * @return property value as an array of <code>String</code>s, or
	 *         <code>null</code>.
	 */
	public String[] getStrings(String name) {
		String valueString = properties.getProperty(name);
		return valueString.split("(,|\\s)+");
	}

	// for test
	public static void main(String[] args) {
		Configuration conf = Configuration.getInstance();

		String filelimit = conf.get("file.content.limit");
		System.out.println("file.content.limit: " + filelimit);

		Float agent = conf.getFloat("http.agent.version", 4.0f);
		System.out.println("http.agent.version: " + agent);

		String agentUrl = conf.get("http.agent.url2");
		System.out.println("http.agent.url2: " + agentUrl);
		
		String[] filters = conf.getStrings("urlnormalizer.order");
		for(String filter: filters)
			System.out.println(filter);
	}
}
